package userInterface.Panels.CommandArgumentPanels;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileFilter;

import logging.*;
import settings.Action.*;
import userInterface.imageCapture.*;

/**
 * {@link userInterface.panels.CommandArgumentPanels.PanelCommandArguments PanelCommandArguments} for editing
 * {@link settings.Action.Action#MOUSEMOVE Action.MOUSEMOVE} {@link settings.Command Command} arguments.
 * @author BDS
 */
public class PanelMouseMoveArguments extends PanelCommandArguments
{
	private static final long serialVersionUID = 1L;
	
	private InputVerifier mouseMoveInputVerifier;
	private ActionListener mouseMoveActionListener;
	
	private GridBagLayout gblMouseMoveArguments;
	
	private JLabel lblMouseMoveTarget;
    private DefaultComboBoxModel<MouseMoveTarget> mouseMoveTargets;
    private JComboBox<MouseMoveTarget> cbxMouseMoveTargets;
	private JLabel lblMouseMoveTargetData1;
    private JFormattedTextField txtX;
    private DefaultComboBoxModel<String> images;
    private JComboBox<String> cbxImages;
    private JButton btnImageBrowse;
    private JButton btnImageCapture;
	private JLabel lblMouseMoveTargetData2;
	private JFormattedTextField txtY;
	private JLabel lblImage;
	
    private Border defaultBorder;
    private Border errorBorder;

	/**
	 * Class constructor.
	 */
	public PanelMouseMoveArguments()
	{
		mouseMoveInputVerifier = new MouseMoveInputVerifier();
		mouseMoveActionListener = new MouseMoveActionListener();
		
		gblMouseMoveArguments = new GridBagLayout();
		gblMouseMoveArguments.columnWidths = new int[] { 100, 0, 0, 0 } ;
		gblMouseMoveArguments.columnWeights = new double[] { 0.0, 1.0, 0.0, 0.0 };
		gblMouseMoveArguments.rowWeights = new double[] { 0.0, 0.0, 0.0, 1.0 };
		setLayout(gblMouseMoveArguments);

		lblMouseMoveTarget = new JLabel("Target:");
        GridBagConstraints gbc_lblMouseMoveTarget = new GridBagConstraints();
        gbc_lblMouseMoveTarget.anchor = GridBagConstraints.WEST;
        gbc_lblMouseMoveTarget.insets = new Insets(0, 0, 5, 5);
        gbc_lblMouseMoveTarget.gridx = 0;
        gbc_lblMouseMoveTarget.gridy = 0;
        add(lblMouseMoveTarget, gbc_lblMouseMoveTarget);
        
        mouseMoveTargets = new DefaultComboBoxModel<MouseMoveTarget>(MouseMoveTarget.values());
        cbxMouseMoveTargets = new JComboBox<MouseMoveTarget>(mouseMoveTargets);
        GridBagConstraints gbc_cbxMouseMoveTargets = new GridBagConstraints();
        gbc_cbxMouseMoveTargets.fill = GridBagConstraints.HORIZONTAL;
        gbc_cbxMouseMoveTargets.insets = new Insets(0, 0, 5, 0);
        gbc_cbxMouseMoveTargets.gridwidth = 3;
        gbc_cbxMouseMoveTargets.gridx = 1;
        gbc_cbxMouseMoveTargets.gridy = 0;
        add(cbxMouseMoveTargets, gbc_cbxMouseMoveTargets);
		
		lblMouseMoveTargetData1 = new JLabel("X:");
		GridBagConstraints gbc_lblMouseMoveTargetData1 = new GridBagConstraints();
		gbc_lblMouseMoveTargetData1.anchor = GridBagConstraints.WEST;
        gbc_lblMouseMoveTargetData1.insets = new Insets(0, 0, 5, 5);
        gbc_lblMouseMoveTargetData1.gridx = 0;
        gbc_lblMouseMoveTargetData1.gridy = 1;
        add(lblMouseMoveTargetData1, gbc_lblMouseMoveTargetData1);
        
        txtX = new JFormattedTextField();
        txtX.setInputVerifier(mouseMoveInputVerifier);
        txtX.addActionListener(mouseMoveActionListener);
        GridBagConstraints gbc_txtX = new GridBagConstraints();
        gbc_txtX.fill = GridBagConstraints.HORIZONTAL;
        gbc_txtX.insets = new Insets(0, 0, 5, 0);
        gbc_txtX.gridwidth = 3;
        gbc_txtX.gridx = 1;
        gbc_txtX.gridy = 1;
        add(txtX, gbc_txtX);
        
        images = new DefaultComboBoxModel<String>();
        cbxImages = new JComboBox<String>(images);
        cbxImages.setVisible(false);
        cbxImages.setActionCommand("Image changed");
        cbxImages.addActionListener(mouseMoveActionListener);
        GridBagConstraints gbc_cbxImages = new GridBagConstraints();
        gbc_cbxImages.fill = GridBagConstraints.HORIZONTAL;
        gbc_cbxImages.insets = new Insets(0, 0, 5, 0);
        gbc_cbxImages.gridx = 1;
        gbc_cbxImages.gridy = 1;
        add(cbxImages, gbc_cbxImages);
        
        btnImageBrowse = new JButton();
        btnImageBrowse.setIcon(new ImageIcon(getClass().getResource("/images/browse_tiny.png")));
        btnImageBrowse.setToolTipText("Browse...");
        btnImageBrowse.setVisible(false);
        btnImageBrowse.setActionCommand("Browse");
        btnImageBrowse.addActionListener(mouseMoveActionListener);
        GridBagConstraints gbc_btnImageBrowse = new GridBagConstraints();
        gbc_btnImageBrowse.insets = new Insets(0, 0, 5, 0);
        gbc_btnImageBrowse.gridx = 2;
        gbc_btnImageBrowse.gridy = 1;
        add(btnImageBrowse, gbc_btnImageBrowse);
        
        btnImageCapture = new JButton();
        btnImageCapture.setIcon(new ImageIcon(getClass().getResource("/images/browse_tiny.png")));
        btnImageCapture.setToolTipText("Capture...");
        btnImageCapture.setVisible(false);
        btnImageCapture.setActionCommand("Capture");
        btnImageCapture.addActionListener(mouseMoveActionListener);
        GridBagConstraints gbc_btnImageCapture = new GridBagConstraints();
        gbc_btnImageCapture.insets = new Insets(0, 0, 5, 0);
        gbc_btnImageCapture.gridx = 3;
        gbc_btnImageCapture.gridy = 1;
        add(btnImageCapture, gbc_btnImageCapture);
		
		lblMouseMoveTargetData2 = new JLabel("Y:");
		GridBagConstraints gbc_lblMouseMoveTargetData2 = new GridBagConstraints();
		gbc_lblMouseMoveTargetData2.anchor = GridBagConstraints.WEST;
        gbc_lblMouseMoveTargetData2.insets = new Insets(0, 0, 5, 5);
        gbc_lblMouseMoveTargetData2.gridx = 0;
        gbc_lblMouseMoveTargetData2.gridy = 2;
        add(lblMouseMoveTargetData2, gbc_lblMouseMoveTargetData2);

		txtY = new JFormattedTextField();
		txtY.setInputVerifier(mouseMoveInputVerifier);
		txtY.addActionListener(mouseMoveActionListener);
		GridBagConstraints gbc_txtY = new GridBagConstraints();
		gbc_txtY.fill = GridBagConstraints.HORIZONTAL;
        gbc_txtX.gridwidth = 3;
        gbc_txtY.gridx = 1;
        gbc_txtY.gridy = 2;
        add(txtY, gbc_txtY);

        lblImage = new JLabel();
        lblImage.setHorizontalAlignment(SwingConstants.CENTER);
        lblImage.setVisible(false);
        GridBagConstraints gbc_lblImage = new GridBagConstraints();
        gbc_lblImage.fill = GridBagConstraints.BOTH;
        gbc_lblImage.insets = new Insets(10, 0, 0, 5);
        gbc_lblImage.gridwidth = 4;
        gbc_lblImage.gridx = 0;
        gbc_lblImage.gridy = 3;
        add(lblImage, gbc_lblImage);
		
		defaultBorder = txtX.getBorder();
		errorBorder = BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.RED), defaultBorder);
		
        loadImageFiles(null);
	}
	
    /**
     * Sets the enabled state of contained controls.
     * @param enabled Determines the enabled state of contained controls.
     */
	@Override
	public void setEnabled(boolean enabled)
	{
        super.setEnabled(enabled);
        
		lblMouseMoveTargetData1.setEnabled(enabled);
        cbxMouseMoveTargets.setEnabled(enabled);
        cbxImages.setEnabled(enabled);
        btnImageBrowse.setEnabled(enabled);
        btnImageCapture.setEnabled(enabled);
        lblImage.setEnabled(enabled);
        
		lblMouseMoveTargetData2.setEnabled(enabled);
		txtY.setEnabled(enabled);
		
		if (enabled)
		{
			if (mouseMoveTargets.getSelectedItem() == null)
			{
				lblMouseMoveTargetData1.setVisible(false);
                txtX.setVisible(false);
                cbxImages.setVisible(false);
                btnImageBrowse.setVisible(false);
                btnImageCapture.setVisible(false);
                lblImage.setVisible(false);
                
				lblMouseMoveTargetData2.setVisible(false);
				txtY.setVisible(false);
			}
			else if (MouseMoveTarget.fromName(mouseMoveTargets.getSelectedItem().toString()) == MouseMoveTarget.COORDINATES)
			{
				lblMouseMoveTargetData1.setText("X:");
                lblMouseMoveTargetData1.setVisible(true);
                txtX.setVisible(true);
                cbxImages.setVisible(false);
                btnImageBrowse.setVisible(false);
                btnImageCapture.setVisible(false);
                lblImage.setVisible(false);
                
				lblMouseMoveTargetData2.setVisible(true);
				txtY.setVisible(true);
			}
			else if (MouseMoveTarget.fromName(mouseMoveTargets.getSelectedItem().toString()) == MouseMoveTarget.IMAGE)
			{
				lblMouseMoveTargetData1.setText("Image:");
                lblMouseMoveTargetData1.setVisible(true);
                txtX.setVisible(false);
                cbxImages.setVisible(true);
                btnImageBrowse.setVisible(true);
                btnImageCapture.setVisible(true);
                lblImage.setVisible(true);
                
				lblMouseMoveTargetData2.setVisible(false);
				txtY.setVisible(false);
			}
		}
	}
	
    /**
     * Provides the external event processing class references to the contained {@link javax.swing.JComponent JComponents}.
     * @param actionListener The {@link java.awt.event.ActionListener ActionListener} for {@link javax.swing.JComboBox JComboBoxes}.
     * @param focusAdapter The {@link java.awt.event.FocusAdapter FocusAdapter} for {@link javax.swing.JFormattedTextField JFormattedTextFields}.
     * @param keyAdapter The {@link java.awt.event.KeyAdapater KeyAdapater} for {@link javax.swing.JFormattedTextField JFormattedTextFields}.
     * @param changeListener The {@link javax.swing.event.ChangeListener ChangeListener} for {@link javax.swing.JSpinner JSpinners}.
     */
	@Override
	public void addActionListeners(ActionListener actionListener, FocusAdapter focusAdapter, KeyAdapter keyAdapter, ChangeListener changeListener)
	{
		cbxMouseMoveTargets.addActionListener(actionListener);
		txtX.addFocusListener(focusAdapter);
		txtX.addKeyListener(keyAdapter);
		txtY.addFocusListener(focusAdapter);
		txtY.addKeyListener(keyAdapter);
		cbxImages.addActionListener(actionListener);
	}
	
    /**
     * Gets the {@link settings.Action.Action#VALIDATE Action.VALIDATE} {@link settings.Command Command} arguments currently displayed.
     * @return The {@link settings.Action.Action#VALIDATE Action.VALIDATE} {@link settings.Command Command} arguments currently displayed.
     */
	@Override
	public List<String> getArguments() 
	{ 
		List<String> arguments = new ArrayList<String>();
		
		if (mouseMoveTargets.getSelectedItem() != null)
		{
			arguments.add(mouseMoveTargets.getSelectedItem().toString());

			if (MouseMoveTarget.fromName(mouseMoveTargets.getSelectedItem().toString()) == MouseMoveTarget.COORDINATES)
			{
				try
				{
					arguments.add(Integer.toString(Integer.parseInt(txtX.getText())));
					arguments.add(Integer.toString(Integer.parseInt(txtY.getText())));
				} 
				catch (NumberFormatException numberFormatException) { }
			}
			else if (MouseMoveTarget.fromName(mouseMoveTargets.getSelectedItem().toString()) == MouseMoveTarget.IMAGE)
			{
				if (images.getSelectedItem() != null)
				{
					arguments.add(images.getSelectedItem().toString());
				}
				else
				{
					arguments.add(null);
				}
			}
		}
		
		return arguments;
	}
	
    /**
     * <ul>
     *     <li>Sets the {@link settings.Action.Action#MOUSEMOVE Action.MOUSEMOVE} {@link settings.Command Command} arguments.</li>
     *     <li>Updates the UI based on the given arguments.</li>
     * </ul>
     * @param arguments The {@link settings.Action.Action#MOUSEMOVE Action.MOUSEMOVE} {@link settings.Command Command} arguments used to update the UI.
     */
	@Override
	public void setArguments(List<String> arguments)
	{
		if (arguments != null &&
			arguments.size() > 0 && 
			arguments.get(0) != null && 
			!arguments.get(0).isEmpty())
		{
			mouseMoveTargets.setSelectedItem(MouseMoveTarget.fromName(arguments.get(0)));
			
			if ((MouseMoveTarget) cbxMouseMoveTargets.getSelectedItem() == MouseMoveTarget.COORDINATES)
			{
				if (arguments.size() > 1 && arguments.get(1) != null && !arguments.get(1).isEmpty()) 
				{
					txtX.setText(arguments.get(1));
				}
				else
				{
					txtX.setText("");
				}
				
				if (arguments.size() > 2 && arguments.get(2) != null && !arguments.get(2).isEmpty()) 
				{
					txtY.setText(arguments.get(2));
				}
				else
				{
					txtY.setText("");
				}

				cbxImages.setSelectedIndex(-1);
			}
			else if ((MouseMoveTarget) cbxMouseMoveTargets.getSelectedItem() == MouseMoveTarget.IMAGE)
			{
				if (arguments.size() > 1 && arguments.get(1) != null && !arguments.get(1).isEmpty()) 
				{
					images.setSelectedItem(arguments.get(1));
				}
				else
				{
					cbxImages.setSelectedIndex(-1);
				}
				
				txtX.setText("");
				txtY.setText("");
			}
		}
		else
		{
			cbxMouseMoveTargets.setSelectedIndex(-1);
			txtX.setText("");
			txtY.setText("");
			cbxImages.setSelectedIndex(-1);
		}
		
		if (arguments.size() > 1 && arguments.get(1) != null) 
		{
			txtY.setText(arguments.get(1));
		}
		else
		{
			txtY.setText("");
		}
	}
	
    /**
     * Loads all image file information from the local image directory.
     * @param imageToSelect The image to select in the {@link javax.swing.JComboBox JComboBox} after loading is complete.
     */
	private void loadImageFiles(String imageToSelect)
	{
		try
		{
			File imageDirectory = new File(System.getProperty("user.dir") + "\\Images");
			if (imageDirectory.exists() || imageDirectory.mkdir())
			{
				images.removeAllElements();
				
				if (imageDirectory.listFiles() != null)
				{
					for (File imageFile : imageDirectory.listFiles()) 
					{
						// Ignore the self and parent aliases.
						if (!imageFile.getName().equals(".") &&
							!imageFile.getName().equals("..")) 
						{
							images.addElement(imageFile.getName());
						}
					}
				}
				
				if (imageToSelect != null)
				{
					images.setSelectedItem(imageToSelect);
				}
			}
		}
		catch (Exception exception)
		{
			Logger.logException(exception);
		}
	}
	
    /**
     * {@link javax.swing.InputVerifier InputVerifier} for {@link userInterface.panels.CommandArgumentPanels.PanelMouseMoveArguments PanelMouseMoveArguments}
     * {@link javax.swing.JFormattedTextField JFormattedTextFields}.
     * @author BDS
     */
	private class MouseMoveInputVerifier extends InputVerifier 
	{		
	      /**
         * Determines if the provided input is valid. 
         * Valid input is defined as being an integer greater than or equal to 0.
         * @param component The {@link javax.swing.JFormattedTextField JFormattedTextField} to verify.
         * @return <ul>
         *             <li><code>true</code> if the input is valid.</li>
         *             <li><code>false</code> if the input is invalid.</li>
         *         </ul>
         */
		@Override
		public boolean verify(JComponent component) 
		{
		    boolean valid = false;
            
            if (component instanceof JFormattedTextField) 
            {
                try
                {
                    valid =  Integer.parseInt(((JFormattedTextField)component).getText()) >= 0;
                } 
                catch (Exception exception) { }
            }
            
            return valid;
		}
		
        /**
         * <ul>
         *     <li>Determines if the provided {@link javax.swing.JComponent JComponent} should yield focus when requested. 
         *         Yielding focus is based on the validity of input.</li>
         *     <li>Sets the border of the provided {@link javax.swing.JComponent JComponent} based on the validity of input.</li>
         * </ul>
         * @param component The {@link javax.swing.JFormattedTextField JFormattedTextField} to yield focus.
         * @returns <ul>
         *              <li><code>true</code> if all input is valid and focus should be yielded.</li>
         *              <li><code>false</code> if any input is invalid and focus should not be yielded.</li>
         *          </ul>
         */
		@Override
		public boolean shouldYieldFocus(JComponent component) 
		{
		    boolean yieldFocus = verify(component);
            
            if (yieldFocus)
            {
                ((JFormattedTextField)component).setBorder(defaultBorder);
            }
            else
            {
                ((JFormattedTextField)component).setBorder(errorBorder);
            }
            
            return yieldFocus;
		}
	}

    /**
     * {@link java.awt.event.ActionListener ActionListener} for {@link userInterface.panels.CommandArgumentPanels.PanelMouseMoveArguments PanelMouseMoveArguments}
     * {@link javax.swing.JComboBox JComboBoxes} and {@link javax.swing.JButton JButtons}.
     * @author BDS
     */
	private class MouseMoveActionListener implements ActionListener
	{
        /**
         * Executes specific logic based on the following {@link java.awt.event.ActionEvent ActionEvents}:
         * <ul>
         *     <li>Image changed - Updates the {@link javax.swing.JLabel JLabel} representing the selected image.</li>
         *     <li>Browse - Opens a {@link javax.swing.JFileChooser JFileChooser} for populating the image {@link javax.swing.JComboBox JComboBox}.</li>
         *     <li>Capture - Utilizes {@link userInterface.imageCapture.ImageCapture ImageCapture} to capture a new validation image.</li>
         * </ul>
         * @param actionEvent The triggering {@link java.awt.event.ActionEvent ActionEvent}.
         */
		@Override
		public void actionPerformed(ActionEvent actionEvent)
		{
		    if (actionEvent.getActionCommand().equals(cbxImages.getActionCommand()))
            {
                lblImage.setIcon(new ImageIcon(System.getProperty("user.dir") + "\\Images\\" + images.getSelectedItem()));
            }
            else if (actionEvent.getActionCommand().equals(btnImageBrowse.getActionCommand()))
            {
                JFileChooser fileChooser = new JFileChooser(System.getProperty("user.dir"));
                fileChooser.setFileFilter(new ImageCheckFileFilter());
                fileChooser.setAcceptAllFileFilterUsed(false);
                if (fileChooser.showDialog(((JButton) actionEvent.getSource()).getParent(), "Add") == JFileChooser.APPROVE_OPTION)
                {
                    try 
                    {
                        File destinationFile = new File(System.getProperty("user.dir") + "\\Images\\" + fileChooser.getSelectedFile().getName());                       
                        Files.copy(fileChooser.getSelectedFile().toPath(), destinationFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
                        loadImageFiles(destinationFile.getName());
                    }
                    catch (IOException exception)
                    {
                        Logger.logException(exception);
                    }
                }
            }
            else if (actionEvent.getActionCommand().equals(btnImageCapture.getActionCommand()))
            {
                loadImageFiles(ImageCapture.captureImage());
            }
		}

		/**
         * {@link javax.swing.filechooser.FileFilter FileFilter} for the
         * {@link userInterface.panels.CommandArgumentPanels.PanelMouseMoveArguments.MouseMoveActionListener MouseMoveActionListener}
         * {@link javax.swing.JFileChooser JFileChooser}.
         * @author BDS
         */
        private class ImageCheckFileFilter extends FileFilter
        {
            /**
             * Accepts only directories or image files (gif, jpg, and png).
             * @param file The {@link java.io.File File} to validate.
             * @return <ul>
             *             <li><code>true</code> if the provided {@link java.io.File File} is a directory or image.</li>
             *             <li><code>false</code> otherwise.</li>
             *         </ul>
             */
            @Override
            public boolean accept(File file)
            {
                return file.isDirectory() ||
                       file.getName().matches(".*?\\.gif") ||
                       file.getName().matches(".*?\\.jpg") ||
                       file.getName().matches(".*?\\.png");
            }

            /**
             * Expresses that only image files are supported.
             * @return Always returns "Image".
             */
            @Override
            public String getDescription()
            {
                return "Image";
            }
        }
	}
}
